module IPFS.Multiaddr.Protocol (
  parse,
  Protocol(..),
  protocolCode,
  protocolName,
  parseMultiHash,
  encode,
  decode,
  getProtocolPort,
  getStringAddress) where

import Prelude

import Control.Alternative ((<|>))
import Control.Monad.Error.Class (throwError)
import Data.Array (some)
import Data.ByteString as BS
import Data.Either (Either(..))
import Data.Maybe (fromJust, isJust)
import Data.Serialize.Get (Get, getBytes, getVarInt)
import Data.String.CodeUnits (fromCharArray)
import Data.VarInt as VI
import IPFS.Multiaddr.IP as IP
import IPFS.Multiaddr.IP.IPv4 as IPv4
import IPFS.Multiaddr.IP.IPv6 as IPv6
import IPFS.Multiaddr.Onion (Onion)
import IPFS.Multiaddr.UnixPath as U
import IPFS.Multihash (Multihash(..), fromB58String, toB58String, toByteString)
import IPFS.Multihash.HashType (fromCode)
import Partial.Unsafe (unsafePartial)
import Text.Parsing.Parser (Parser, fail)
import Text.Parsing.Parser.Combinators (try)
import Text.Parsing.Parser.String (satisfy, string)
import Type.Quotient (mkQuotient)
import Undefined (undefined)


data Protocol = IP4 IPv4.IPv4 | IP6 IPv6.IPv6 | TCP IP.Port | UDP IP.Port | DCCP IP.Port | SCTP IP.Port | ONION Onion |
                IPFS Multihash | P2P Multihash | Unix U.UnixPath | UTP | UDT | QUIC | HTTP | HTTPS | WS | WSS

protocolCode::Protocol -> Int
protocolCode (IP4 _)    = 4
protocolCode (IP6 _)    = 41
protocolCode (TCP _)    = 6
protocolCode (UDP _)    = 17
protocolCode (DCCP _)   = 33
protocolCode (SCTP _)   = 132
protocolCode (ONION _)  = 444
protocolCode (IPFS _)   = 421
protocolCode (P2P _)    = 420
protocolCode (Unix _)   = 400
protocolCode UTP  = 302
protocolCode UDT  = 301
protocolCode QUIC  = 81
protocolCode HTTP  = 480
protocolCode HTTPS  = 443
protocolCode WS  = 477
protocolCode WSS  = 478

protocolName :: Protocol -> String
protocolName (IP4 _)   = "ip4"
protocolName (IP6 _)   = "ip6"
protocolName (TCP _)   = "tcp"
protocolName (UDP _)   = "udp"
protocolName (DCCP _)  = "dccp"
protocolName (SCTP _)  = "sctp"
protocolName (ONION _) = "onion"
protocolName (IPFS _)  = "ipfs"
protocolName (P2P _)   = "p2p"
protocolName (Unix _)  = "unix"
protocolName UTP  = "utp"
protocolName UDT  = "udt"
protocolName QUIC  = "quic"
protocolName HTTP  = "http"
protocolName HTTPS  = "https"
protocolName WS  = "ws"
protocolName WSS  = "wss"


instance showProtocol :: Show Protocol where
  show p = "/" <> protocolName p <> "/" <> go p
   where
     go (IP4 ip4)     = show ip4
     go (IP6 ip6)     = show ip6
     go (TCP tcp)     = show tcp
     go (UDP udp)     = show udp
     go (DCCP dccp)   = show dccp
     go (SCTP sctp)   = show sctp
     go (ONION onion) = show onion
     go (IPFS ipfs)   = toB58String ipfs
     go (P2P p2p)     = show p2p
     go (Unix unix)   = show unix
     go _ = ""


getProtocolPort::Protocol -> Either String IP.Port
getProtocolPort (TCP p)  = Right p 
getProtocolPort (UDP p)  = Right p 
getProtocolPort (DCCP p) = Right p 
getProtocolPort (SCTP p) = Right p 
getProtocolPort  other   = Left $ (protocolName other) <> "not port type"

getStringAddress ::Protocol -> Either String String
getStringAddress (IP4 i4) = Right $ show i4
getStringAddress (IP6 i6) = Right $ show i6
getStringAddress _ = Left  "Must have a valid family name: {ip4, ip6, dns4, dns6}."
{-
/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
-}
parse::Parser String Protocol
parse = parseProtocl (IP4 undefined)  *> (IP4 <$> IPv4.parse)       <|>
        parseProtocl (IP6 undefined)  *> (IP6 <$> IPv6.parse)       <|>
        parseProtocl (TCP undefined)  *> (TCP <$> IP.parsePort)     <|>
        parseProtocl (UDP undefined)  *> (UDP <$> IP.parsePort)     <|>
        parseProtocl (DCCP undefined) *> (DCCP <$> IP.parsePort)    <|>
        parseProtocl (SCTP undefined) *> (SCTP <$> IP.parsePort)    <|>
        parseProtocl (IPFS undefined) *> (IPFS <$> parseMultiHash)  <|>
        parseProtocl (P2P undefined)  *>  (P2P <$> parseMultiHash)  <|>
        parseProtocl UTP   *>  pure UTP   <|>
        parseProtocl UDT   *>  pure UDT   <|>
        parseProtocl QUIC  *>  pure QUIC  <|>
        parseProtocl HTTP  *>  pure HTTP  <|>
        parseProtocl HTTPS *>  pure HTTPS <|>
        parseProtocl WS    *>  pure WS    <|>
        parseProtocl WSS   *>  pure WSS
  where
    parseProtocl::Protocol -> Parser String String
    parseProtocl p = try (string ("/"<> protocolName p <>"/"))

parseMultiHash::Parser String Multihash
parseMultiHash = do
 (str::Array Char) <- some $ satisfy (\s -> s /= '/')
 let jstr = fromCharArray str
 let mh = fromB58String jstr
 case mh of
  Left  l -> fail l
  Right r -> pure r


encodePCode::Protocol -> BS.ByteString
encodePCode v = BS.pack $ map (mkQuotient) $ VI.encode (protocolCode v)


encode::Protocol -> BS.ByteString
encode v@(IP4 ip4)   = (encodePCode v)  <> IPv4.encode ip4
encode v@(IP6 ip6)   = (encodePCode v)  <> IPv6.encode ip6
encode v@(TCP port)  = (encodePCode v)  <> IP.encodePort port
encode v@(UDP port)  = (encodePCode v)  <> IP.encodePort port
encode v@(DCCP port) = (encodePCode v)  <> IP.encodePort port
encode v@(SCTP port) = (encodePCode v)  <> IP.encodePort port
encode v@(IPFS hash) = (encodePCode v)  <> toByteString hash
encode v@(P2P hash) =  (encodePCode v)  <> toByteString hash
encode v@(Unix path) =  (encodePCode v)  <> U.encode path
encode  _          = BS.empty

decode::Get Protocol
decode  = do
   code <- getVarInt
   decodeProtocol code
 where
   decodeProtocol::Int -> Get Protocol
   decodeProtocol code  | (eq (protocolCode (IP4 undefined))  code)  =  IP4  <$> IPv4.decode
                        | (eq (protocolCode (IP6 undefined))  code)   = IP6  <$>  IPv6.decode
                        | (eq (protocolCode (TCP undefined))  code)   = TCP  <$> IP.decodePort
                        | (eq (protocolCode (UDP undefined))  code)   = UDP  <$>  IP.decodePort
                        | (eq (protocolCode (DCCP undefined))  code)  = DCCP <$>  IP.decodePort
                        | (eq (protocolCode (SCTP undefined))  code)  = SCTP <$> IP.decodePort
                        | (eq (protocolCode (IPFS undefined))  code)   = IPFS <$> decodeHash
                        | (eq (protocolCode (P2P undefined))   code)   = P2P  <$> decodeHash
                        --  | (eq (protocolCode (Unix undefined))  code)   = pure $ Unix $ U.decode bs
                         | otherwise = throwError "not support protocol type"

decodeHash::Get Multihash
decodeHash = do
    code <- getVarInt
    _ <- when (not $ isValidCode  code) (throwError $ "multihash unknown function code: 0x"<> show code)
    bufLen <- getVarInt
    _ <- when (bufLen < 1) (throwError $ "multihash invalid length: 0x"<> show bufLen)
    unCodeUnSizeBuff <- getBytes bufLen
    let hashType = unsafePartial $ fromJust $ fromCode code
    pure $ Multihash {type:hashType,hash:unCodeUnSizeBuff}


isValidCode::Int -> Boolean
isValidCode n = n > 0 && n < 0x10 || (isJust $ fromCode n) 